"use strict";
exports.__esModule = true;
/*
 * tunnel.js 内网穿透隧道
 * 作者：HUANG ZHENJIE huangzhenjie@hzj.ac.cn
 * 日期：2023-06-14
 * 版本：1.0
 * 描述：此代码实现了一个简单的 内网穿透服务器和内网穿透客户端，将互联网的流量准发到内网。
 *
 * 用法：
 *   服务器模式: tunnel -l <本地主机> <本地端口> <映射主机> <映射端口>
 *   客户端模式: tunnel -c <转发主机> <转发端口> <目标主机> <目标端口> <用户名> <密码>
 */
var net = require("net");
function unpack(frame) {
    var match = frame.match(/<([^>]*)>\[(.*):(.*)\]\[(.*):(.*)\]([\s\S]*)<\/\1>/);
    if (match) {
        var type = match[1], from_host = match[2], from_port = match[3], to_host = match[4], to_port = match[5], payload = match[6];
        return {
            type: type,
            payload: encode_payload(payload),
            from_host: from_host,
            from_port: parseInt(from_port),
            to_host: to_host,
            to_port: parseInt(to_port)
        };
    }
    else {
        throw new Error('无效的数据帧');
    }
}
function pack(data) {
    var type = data.type, payload = data.payload, from_host = data.from_host, from_port = data.from_port, to_host = data.to_host, to_port = data.to_port;
    return "<".concat(type, ">[").concat(from_host, ":").concat(from_port, "][").concat(to_host, ":").concat(to_port, "]").concat(decode_payload(payload), "</").concat(type, ">");
}
function decode_payload(payload) {
    if (typeof payload === 'object') {
        return JSON.stringify(payload);
    }
    return payload;
}
function encode_payload(payload) {
    try {
        return JSON.parse(payload);
    }
    catch (e) {
        return payload;
    }
}
var users = [
    {
        username: 'huangzhenjie',
        password: '123456',
        listen_host: '0.0.0.0',
        listen_port: 4480
    }
];
var userService = new /** @class */ (function () {
    function class_1() {
    }
    class_1.prototype.find_user_by_username = function (username) {
        return users.find(function (user) { return user.username === username; });
    };
    class_1.prototype.auth_user = function (username, password) {
        return users.find(function (user) { return user.username === username && user.password === password; });
    };
    class_1.prototype.add_user = function (user) {
        users.push(user);
        return user;
    };
    return class_1;
}());
var sessions = [];
var sessionService = new /** @class */ (function () {
    function class_2() {
    }
    class_2.prototype.add_session = function (session) {
        sessions.push(session);
        console.info("[*] \u670D\u52A1\u7AEF\u6DFB\u52A0\u4E00\u4E2A\u4F1A\u8BDD(".concat(session.name, "-").concat(session.socket.remoteAddress, ":").concat(session.socket.remotePort, ")"));
        return session;
    };
    class_2.prototype.find_session_by_name = function (name) {
        return sessions.find(function (session) { return session.name === name; });
    };
    class_2.prototype.remove_session_by_remote = function (host, port) {
        var session = sessions.find(function (session) { return session.socket.remotePort === port && session.socket.remoteAddress === host; });
        var index = sessions.indexOf(session);
        if (session && index !== -1) {
            sessions.splice(index, 1);
            console.info("[*] \u670D\u52A1\u7AEF\u79FB\u9664\u4E00\u4E2A\u4F1A\u8BDD(".concat(session.name, "-").concat(session.socket.remoteAddress, ":").concat(session.socket.remotePort, ")"));
            return;
        }
        console.warn("[!] \u670D\u52A1\u7AEF\u79FB\u9664\u4F1A\u8BDD\u5931\u8D25(".concat(host, ":").concat(port, ")\uFF0C\u672A\u627E\u5230\u3002"));
    };
    class_2.prototype.find_session_by_remote = function (host, port) {
        return sessions.find(function (session) { return session.socket.remotePort === port && session.socket.remoteAddress === host; });
    };
    return class_2;
}());
var FrameConstants = {
    AUTH: 'auth',
    END_SIGNAL: 'end',
    FORWARD: 'forward',
    START_DELIMITER: '<\n',
    END_DELIMITER: '>\n',
    DELIMITER: '|0x01|0x02|'
};
var ClientConnectHandler = /** @class */ (function () {
    function ClientConnectHandler(config) {
        this.config = config;
    }
    ClientConnectHandler.prototype.handle_socket = function (socket) {
        var _this = this;
        console.log("[*] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u4E0E \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u5EFA\u7ACB\u8FDE\u63A5\u3002"));
        socket.on('data', (function (data) { return _this.handle_data(data, socket); }));
        socket.on('error', (function (error) { return _this.handle_error(error, socket); }));
        socket.on('close', function () { return _this.handle_close(socket); });
    };
    ClientConnectHandler.prototype.handle_data = function (data, socket) {
        console.info("[*] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u63A5\u6536 \u6765\u81EA\u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
        this.buffer += data.toString();
        var frameRegex = /<([^\s>]+)>[\s\S]*?<\/\1>/g;
        var match;
        while ((match = frameRegex.exec(this.buffer)) !== null) {
            var frame = match[0];
            this.buffer = this.buffer.substring(match.index + frame.length);
            this.handle_frame(frame, socket);
        }
    };
    ClientConnectHandler.prototype.handle_frame = function (frame, socket) {
        var chunk = unpack(frame);
        console.log(chunk);
        if (chunk.type === FrameConstants.AUTH) {
            this.handle_auth(chunk, socket);
            return;
        }
        if (chunk.type === FrameConstants.FORWARD) {
            this.handle_forward(chunk, socket);
            return;
        }
        if (chunk.type === FrameConstants.END_SIGNAL) {
            this.handle_end_signal(chunk, socket);
            return;
        }
    };
    ClientConnectHandler.prototype.handle_forward = function (chunk, socket) {
        var to_host = chunk.to_host, to_port = chunk.to_port, payload = chunk.payload;
        var data = Buffer.from(payload, 'base64');
        console.info("[*] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u8F6C\u53D1 \u6765\u81EA\u96A7\u9053\u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
        var session = sessionService.find_session_by_remote(to_host, to_port);
        if (!session) {
            console.warn("[!] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u8F6C\u53D1 \u5230(").concat(to_host, ":").concat(to_port, ") \u7684\u5BA2\u6237\u7AEF\u5931\u8D25\uFF0Csocket\u4E0D\u5B58\u5728\u3002"));
            return;
        }
        session.socket.write(data);
    };
    ClientConnectHandler.prototype.handle_end_signal = function (chunk, socket) {
        var to_host = chunk.to_host, to_port = chunk.to_port;
        console.info("[*] ".concat(this.config.name, " \u96A7\u9053\u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u53D1\u9001\u65AD\u5F00\u8FDE\u63A5\u4FE1\u53F7\u3002"));
        var session = sessionService.find_session_by_remote(to_host, to_port);
        if (!session) {
            console.warn("[!] ".concat(this.config.name, " \u65AD\u5F00\u8FDE\u63A5(").concat(to_host, ":").concat(to_port, ") \u7684\u5BA2\u6237\u7AEF\u5931\u8D25\uFF0Csocket\u4E0D\u5B58\u5728\u3002"));
            return;
        }
        session.socket.end();
        sessionService.remove_session_by_remote(to_host, to_port);
    };
    ClientConnectHandler.prototype.handle_auth = function (chunk, socket) {
        var credentials = chunk.payload;
        var user = userService.auth_user(credentials.username, credentials.password);
        if (!user) {
            socket.write(pack({
                type: FrameConstants.AUTH,
                payload: { success: false, message: '用户名或密码错误' },
                to_host: socket.remoteAddress,
                to_port: socket.remotePort,
                from_host: socket.localAddress,
                from_port: socket.localPort
            }));
            console.warn("[!] ".concat(this.config.name, " \u672A\u6388\u6743\u7684\u7528\u6237\u5C1D\u8BD5\u8FDE\u63A5\u3002"));
            socket.end();
            return;
        }
        var session = sessionService.find_session_by_name(user.username);
        if (session) {
            socket.write(pack({
                type: FrameConstants.AUTH,
                payload: { success: false, message: '已经在其他地方登录' },
                to_host: socket.remoteAddress,
                to_port: socket.remotePort,
                from_host: socket.localAddress,
                from_port: socket.localPort
            }));
            console.warn("[!] ".concat(this.config.name, " \u91CD\u590D\u767B\u5F55\u3002"));
            socket.end();
            return;
        }
        sessionService.add_session({
            name: user.username,
            socket: socket,
            to_host: socket.remoteAddress,
            to_port: socket.remotePort,
            from_host: socket.localAddress,
            from_port: socket.localPort
        });
        socket.write(pack({
            type: FrameConstants.AUTH,
            to_host: socket.remoteAddress,
            to_port: socket.remotePort,
            from_host: socket.localAddress,
            from_port: socket.localPort,
            payload: JSON.stringify({
                success: true,
                message: '连接成功',
                host: user.listen_host,
                port: user.listen_port
            })
        }));
        console.log("[*] ".concat(this.config.name, " \u7528\u6237 ").concat(user.username, " \u8BA4\u8BC1\u6210\u529F\u3002"));
        return;
    };
    ClientConnectHandler.prototype.handle_error = function (error, socket) {
        console.error("[x] ".concat(this.config.name, " \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u53D1\u751F\u5F02\u5E38\u65AD\u5F00"), error);
        socket.end();
        sessionService.remove_session_by_remote(socket.remoteAddress, socket.remotePort);
    };
    ClientConnectHandler.prototype.handle_close = function (socket) {
        console.log("[*] ".concat(this.config.name, " \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u65AD\u5F00\u8FDE\u63A5"));
        sessionService.remove_session_by_remote(socket.remoteAddress, socket.remotePort);
    };
    return ClientConnectHandler;
}());
var ForwardConnectHandler = /** @class */ (function () {
    function ForwardConnectHandler(config) {
        this.config = config;
    }
    ForwardConnectHandler.prototype.handle_socket = function (socket) {
        var _this = this;
        console.log("[*] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u4E0E \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u5EFA\u7ACB\u8FDE\u63A5\u3002"));
        var session = sessionService.find_session_by_name(this.config.user);
        if (!session) {
            console.warn("[!] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u76EE\u6807\u96A7\u9053\u5BA2\u6237\u7AEF\u8FDE\u63A5\u4E0D\u5B58\u5728\u3002"));
            socket.end();
            return;
        }
        sessionService.add_session({
            name: "FORWARD_SOCKET[".concat(socket.remoteAddress, ":").concat(socket.remotePort, "]"),
            socket: socket,
            to_host: session.from_host,
            to_port: session.from_port,
            from_host: socket.remoteAddress,
            from_port: socket.remotePort
        });
        socket.on('data', (function (data) { return _this.handle_data(data, socket); }));
        socket.on('error', (function (error) { return _this.handle_error(error, socket); }));
        socket.on('close', function () { return _this.handle_close(socket); });
    };
    ForwardConnectHandler.prototype.handle_data = function (data, socket) {
        console.info("[*] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u63A5\u6536 \u6765\u81EA\u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
        var session = sessionService.find_session_by_name(this.config.user);
        if (!session) {
            console.warn("[!] ".concat(this.config.name, " \u670D\u52A1\u5668(").concat(socket.localAddress, ":").concat(socket.localPort, ") \u76EE\u6807\u96A7\u9053\u5BA2\u6237\u7AEF\u8FDE\u63A5\u4E0D\u5B58\u5728\u3002"));
            socket.end();
        }
        session.socket.write(pack({
            from_host: socket.remoteAddress,
            from_port: socket.remotePort,
            to_host: session.from_host,
            to_port: session.from_port,
            payload: data.toString('base64'),
            type: FrameConstants.FORWARD
        }));
    };
    ForwardConnectHandler.prototype.handle_error = function (error, socket) {
        console.error("[x] ".concat(this.config.name, " \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u53D1\u751F\u5F02\u5E38\u65AD\u5F00"), error);
        socket.end();
        sessionService.remove_session_by_remote(socket.remoteAddress, socket.remotePort);
        var session = sessionService.find_session_by_name(this.config.user);
        if (!session)
            return;
        session.socket.write(pack({
            from_host: socket.remoteAddress,
            from_port: socket.remotePort,
            to_host: session.from_host,
            to_port: session.from_port,
            payload: '',
            type: FrameConstants.END_SIGNAL
        }));
    };
    ForwardConnectHandler.prototype.handle_close = function (socket) {
        console.log("[*] ".concat(this.config.name, " \u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u65AD\u5F00\u8FDE\u63A5"));
        sessionService.remove_session_by_remote(socket.remoteAddress, socket.remotePort);
        var session = sessionService.find_session_by_name(this.config.user);
        if (!session)
            return;
        session.socket.write(pack({
            from_host: socket.remoteAddress,
            from_port: socket.remotePort,
            to_host: session.from_host,
            to_port: session.from_port,
            payload: '',
            type: FrameConstants.END_SIGNAL
        }));
    };
    return ForwardConnectHandler;
}());
var TunnelServerImpl = /** @class */ (function () {
    function TunnelServerImpl() {
    }
    Object.defineProperty(TunnelServerImpl.prototype, "config", {
        get: function () {
            return this._config;
        },
        set: function (value) {
            this._config = value;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(TunnelServerImpl.prototype, "socketEventListener", {
        get: function () {
            return this._socketEventListener;
        },
        set: function (value) {
            this._socketEventListener = value;
        },
        enumerable: false,
        configurable: true
    });
    TunnelServerImpl.prototype.listen = function (host, port) {
        var _this = this;
        this.server = net.createServer(function (socket) {
            _this._socketEventListener.handle_socket(socket);
        });
        this.server.listen(port || this._config.port, host || this._config.host, function () {
            console.log("[*] ".concat(_this._config.name, " listening on ").concat(_this._config.host, ":").concat(_this._config.port));
        });
        return this.server;
    };
    return TunnelServerImpl;
}());
var TunnelClientImpl = /** @class */ (function () {
    function TunnelClientImpl(target_host, target_port) {
        this.buffer = "";
        this.target_host = target_host;
        this.target_port = target_port;
    }
    TunnelClientImpl.prototype.auth = function (username, password) {
        this.socket.write(pack({
            type: FrameConstants.AUTH,
            payload: {
                username: username,
                password: password
            },
            to_host: this.socket.remoteAddress,
            to_port: this.socket.remotePort,
            from_host: this.socket.localAddress,
            from_port: this.socket.localPort
        }));
    };
    TunnelClientImpl.prototype.connect = function (host, port, username, password) {
        var _this = this;
        console.log('[*] CLIENT 正在连接隧道服务器。');
        this.socket = net.createConnection(port, host);
        this.socket.on('connect', function () { return _this.auth(username, password); });
        this.socket.on('data', (function (data) { return _this.handle_data(data, _this.socket); }));
        this.socket.on('error', (function (error) { return _this.handle_error(error, _this.socket); }));
        this.socket.on('close', function () { return _this.handle_close(_this.socket); });
        return this.socket;
    };
    TunnelClientImpl.prototype.handle_data = function (data, socket) {
        console.info("[*] CLIENT \u63A5\u6536\u6765\u81EA\u670D\u52A1\u5668(".concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
        this.buffer += data.toString();
        var frameRegex = /<([^\s>]+)>[\s\S]*?<\/\1>/g;
        var match;
        while ((match = frameRegex.exec(this.buffer)) !== null) {
            var frame = match[0];
            this.buffer = this.buffer.substring(match.index + frame.length);
            this.handle_frame(frame, socket);
        }
    };
    TunnelClientImpl.prototype.handle_frame = function (frame, socket) {
        var chunk = unpack(frame);
        console.log(chunk);
        if (chunk.type === FrameConstants.AUTH) {
            if (!chunk.payload.success) {
                console.warn('[!] CLIENT ' + chunk.payload.message);
                socket.end();
                return;
            }
            console.log("[*] CLIENT [".concat(socket.localAddress, ":").concat(socket.localPort, "] \u5DF2\u6620\u5C04\u5230").concat(chunk.payload.host, " ").concat(chunk.payload.port));
        }
        if (chunk.type === FrameConstants.FORWARD) {
            this.handle_forward(chunk, socket);
            return;
        }
        if (chunk.type === FrameConstants.END_SIGNAL) {
            this.handle_end_signal(chunk, socket);
            return;
        }
    };
    TunnelClientImpl.prototype.handle_forward = function (chunk, socket) {
        var from_host = chunk.from_host, from_port = chunk.from_port, payload = chunk.payload;
        var data = Buffer.from(payload, 'base64');
        console.info("[*] CLIENT \u5BA2\u6237\u7AEF(".concat(socket.localAddress, ":").concat(socket.localPort, ") \u8F6C\u53D1\u6765\u81EA\u670D\u52A1\u5668(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
        if (!this.target_socket || this.target_socket.destroyed) {
            this.target_socket = net.createConnection(this.target_port, this.target_host);
            var target_host_1 = this.target_host;
            var target_port_1 = this.target_port;
            this.target_socket.on('data', function (data) {
                console.info("[*] CLIENT \u5BA2\u6237\u7AEF(".concat(socket.localAddress, ":").concat(socket.localPort, ") \u8F6C\u53D1\u6765\u81EA\u5BA2\u6237\u7AEF(").concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u7684\u6570\u636E\uFF0C\u957F\u5EA6\uFF1A").concat(data.length, " \u5B57\u8282\u3002"));
                socket.write(pack({
                    type: FrameConstants.FORWARD,
                    payload: data.toString('base64'),
                    from_host: target_host_1,
                    from_port: target_port_1,
                    to_port: from_port,
                    to_host: from_host
                }));
            });
            this.target_socket.on('error', function (error) {
                socket.write(pack({
                    type: FrameConstants.END_SIGNAL,
                    payload: '',
                    from_host: target_host_1,
                    from_port: target_port_1,
                    to_port: from_port,
                    to_host: from_host
                }));
            });
            this.target_socket.on('close', function () {
                socket.write(pack({
                    type: FrameConstants.END_SIGNAL,
                    payload: '',
                    from_host: target_host_1,
                    from_port: target_port_1,
                    to_port: from_port,
                    to_host: from_host
                }));
            });
        }
        console.log("\u5957\u63A5\u5B57\u72B6\u6001 ".concat(this.target_socket.destroyed));
        this.target_socket.write(data);
    };
    TunnelClientImpl.prototype.handle_end_signal = function (chunk, socket) {
        console.info("[*] CLIENT \u670D\u52A1\u5668(".concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u53D1\u9001\u65AD\u5F00\u8FDE\u63A5\u4FE1\u53F7\u3002"));
        if (this.target_socket) {
            this.target_socket.end();
            this.target_socket = null;
        }
    };
    TunnelClientImpl.prototype.handle_error = function (error, socket) {
        console.error("[x] CLIENT \u670D\u52A1\u5668(".concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u53D1\u751F\u5F02\u5E38\u65AD\u5F00"), error);
        socket.end();
        this.target_socket = null;
    };
    TunnelClientImpl.prototype.handle_close = function (socket) {
        console.log("[*] CLIENT \u670D\u52A1\u5668(".concat(socket.remoteAddress, ":").concat(socket.remotePort, ") \u65AD\u5F00\u8FDE\u63A5"));
        this.target_socket = null;
    };
    return TunnelClientImpl;
}());
function start_server(host, port) {
    var tunnelServer = new TunnelServerImpl();
    tunnelServer.config = {
        name: 'MAIN',
        host: host,
        port: port,
        user: 'LOCALHOST'
    };
    tunnelServer.socketEventListener = new ClientConnectHandler(tunnelServer.config);
    tunnelServer.listen();
    users.forEach(function (user) {
        var userTunnelServer = new TunnelServerImpl();
        userTunnelServer.config = {
            name: "FORWARD_SERVER-".concat(user.listen_host, ":").concat(user.listen_port),
            port: user.listen_port,
            host: user.listen_host,
            user: user.username
        };
        userTunnelServer.socketEventListener = new ForwardConnectHandler(userTunnelServer.config);
        userTunnelServer.listen();
    });
}
function client_sender(local_host, local_port, remote_host, remote_port, username, password) {
    var tunnelClient = new TunnelClientImpl(local_host, local_port);
    tunnelClient.connect(remote_host, remote_port, username, password);
}
function main() {
    console.log('tunnel.3.js 1.0');
    console.log('(c) HUANG ZHENJIE \n');
    if (process.argv[2] === '-l') {
        start_server(process.argv[3], parseInt(process.argv[4]));
    }
    else if (process.argv[2] === '-c') {
        client_sender(process.argv[3], parseInt(process.argv[4]), process.argv[5], parseInt(process.argv[6]), process.argv[7], process.argv[8]);
    }
    else {
        console.log('用法:');
        console.log('  服务器模式: tunnel -l <本地主机> <本地端口>');
        console.log('  客户端模式: tunnel -c <本地主机> <本地端口> <目标主机> <目标端口> <用户名> <密码>');
    }
}
main();
